# @Time : 2021/2/11 15:31
# @Author : Li Kunlun
# @Description : 寻找函数中的最大值

import numpy as np
import matplotlib.pyplot as plt

DNA_SIZE = 10  # DNA length（0和1的个数取10个单位）
POP_SIZE = 100  # population size（种群-人数）
CROSS_RATE = 0.8  # mating probability (DNA crossover) 有%80的进行交叉配对
MUTATION_RATE = 0.003  # mutation probability
N_GENERATIONS = 200  # 执行迭代的次数
X_BOUND = [0, 5]  # x lower and upper bounds(取值fa)


def F(x):
    return np.sin(10 * x) * x + np.cos(2 * x) * x  # to find the maximum of this function


# find non-zero fitness for selection
# 以高度作为适应度（保持为一个正数）
def get_fitness(pred): return pred + 1e-3 - np.min(pred)


# convert binary DNA to decimal and normalize it to a range(0, 5)
# 翻译DNA，将0，1二进制转换为实数
# [0 1 0 0 1 0 0 1 0 0] ^[2^9,2^8....2^0]/(2^10 - 1) *5
def translateDNA(pop):
    return pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * X_BOUND[1]


# 对crossover和mutate中产生的种群进行选择
def select(pop, fitness):  # nature selection with pop's fitness
    # 有更多机会（p参数：选择比例）选择适应度高的
    # 从pop中抽取size个数据，这里对应的是一个索引值。
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True, p=fitness / fitness.sum())
    return pop[idx]


# 父母两个DNA进行配对，产生出DNA
# 依次从pop种群中选择一个作为父亲，随机和备份的pop中的进行杂交配对，产生下一代。
# 杂交的概率为%80，杂交的位置是随机的。
def crossover(parent, pop):  # mating process (genes crossover)
    if np.random.rand() < CROSS_RATE:
        # [0 0 1 0 0 1 0 0 1 0]
        print("parent--", parent)
        i_ = np.random.randint(0, POP_SIZE, size=1)  # select another individual from pop
        cross_points = np.random.randint(0, 2, size=DNA_SIZE).astype(np.bool)  # choose crossover points
        parent[cross_points] = pop[i_, cross_points]  # mating and produce one child
        print("i_ --", i_)
        # i_ -- [95]对应pop位置的DNA表示为：[1 0 0 0 0 0 1 1 0 1]选取对应True位置上的值
        # cross_points [ True  True  True False  True False  True False False False]
        print("cross_points", cross_points)
        # pop[i_, cross_points] [1 0 0 0 1]
        print("pop[i_, cross_points]", pop[i_, cross_points])
        # parent-- [1 0 0 0 0 1 1 0 1 0]
        print("parent--", parent)
    return parent


# 在孩子中的DNA中随机选择一部分进行变异
def mutate(child):
    for point in range(DNA_SIZE):
        if np.random.rand() < MUTATION_RATE:
            child[point] = 1 if child[point] == 0 else 0
    return child


if __name__ == '__main__':
    # 初始化DNA
    # POP_SIZE*DNA_SIZE的（0，1）矩阵
    # [[0 1 0 0 1 0 0 1 0 0]
    # [0 1 1 1 0 0 1 1 1 0]
    # #  [0, low-->2)区间的整数
    pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE))  # initialize the pop DNA


    plt.ion()  # something about plotting 开启交互模式
    # 迭代次数
    for _ in range(N_GENERATIONS):
        F_values = F(translateDNA(pop))  # compute function value by extracting DNA

        # something about plotting
        plt.scatter(translateDNA(pop), F_values, s=200, lw=0, c='red', alpha=0.5)
        x = np.linspace(*X_BOUND, 200)
        plt.plot(x, F(x))
        plt.pause(0.05)

        # GA part (evolution)
        fitness = get_fitness(F_values)
        # 这是计算的最优值
        print("Most fitted DNA: ", pop[np.argmax(fitness), :])
        # 选择另外一批population
        pop = select(pop, fitness)
        pop_copy = pop.copy()
        # 交叉和变异
        for parent in pop:
            child = crossover(parent, pop_copy)
            child = mutate(child)
            parent[:] = child  # parent is replaced by its child
    plt.ioff()
    plt.show()

